Dans cet article, nous abordons la problématique de l’évaluation client. Nous mettrons en évidence les raisons pour lesquelles il est important pour une entreprise de connaître les habitudes de consommation de ses clients. À travers l’étude d’un cas pratique, nous présenterons les étapes permettant de segmenter une base client afin d’analyser leur comportement de consommation.
La RFM (Recency, Frequency, Monetary Analysis), ou analyse RFM, est une méthode de segmentation et de ciblage comportemental qui permet aux entreprises de classer et de segmenter leurs clients en fonction de trois critères : la récence, la fréquence et le montant des transactions. Cette approche aide les marketeurs et les dirigeants de PME à identifier leur audience cible et à optimiser l’utilisation de leur budget marketing.
Cette méthode attribue des notes aux clients en fonction de 3 facteurs :
Récence : la récence correspond à la date du dernier achat des clients. Ceux qui en ont effectué un récemment, généralement au cours des dernières semaines, ont toujours le produit et la marque en tête. Ils sont donc plus susceptibles d’effectuer un nouvel achat. Vous pouvez mesurer la récence si c’est pertinent dans votre cas. Néanmoins, il est important de noter que pour certaines entreprises, les clients ne commandent pas tous les quatre matins. Par exemple, un constructeur automobile peut vendre une seule voiture à quelqu’un en dix ans.
Fréquence : il s’agit de la fréquence à laquelle le client réalise des transactions, ce qui peut vous aider à identifier les clients réguliers. Par exemple, nombreux sont ceux qui effectuent des achats récurrents et fréquents dans un délai défini. C’est un critère essentiel pour déterminer les personnes les plus susceptibles de choisir à nouveau votre marque après une première acquisition.
Montant : cet élément fait référence à la valeur monétaire qu’un client dépense sur une période donnée. Il est toujours important d’en tenir compte, car cela peut vous donner des indications sur le comportement des consommateurs. Par exemple, vous constaterez peut-être que les clients présentant le montant le plus élevé n’achètent pas d’articles aussi souvent que les autres, mais acquièrent généralement les produits les plus chers.
Les données de chaque facteur permettent aux entreprises de fournir une analyse objective et de déterminer quelle audience cibler pour optimiser leurs campagnes publicitaires et marketing. La plupart des entreprises utilisent une échelle comprise entre 1 et 5, mais vous pouvez choisir toutes les valeurs qui vous semblent nécessaires et utiles pour évaluer vos clients.
J’ai utilisé les données de vente en ligne de Kaggle, qui comprennent différentes colonnes telles que le numéro de facture, la date de la facture, l’identifiant client, le prix de vente, la quantité, etc. Ces données seront très utiles pour l’analyse. Voici les données de vente en ligne : onlineRetailData.
# Charger les bibliothèques nécessaires
suppressMessages(library(dplyr)) # Manipulation de données
suppressMessages(library(data.table)) # Alternative pour la manipulation de données
suppressMessages(library(ggplot2)) # Visualisation
suppressMessages(library(lubridate)) # Manipulation des dates et heures
suppressMessages(library(caret)) # Modélisation et apprentissage automatique
suppressMessages(library(cluster)) # Clustering et silhouette
Retail_DF <- read.csv("~/Desktop/TD R/OnlineRetail.csv", sep = ",")
head(Retail_DF, 5)
## InvoiceNo StockCode Description Quantity
## 1 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6
## 2 536365 71053 WHITE METAL LANTERN 6
## 3 536365 84406B CREAM CUPID HEARTS COAT HANGER 8
## 4 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6
## 5 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6
## InvoiceDate UnitPrice CustomerID Country
## 1 12/1/2010 8:26 2.55 17850 United Kingdom
## 2 12/1/2010 8:26 3.39 17850 United Kingdom
## 3 12/1/2010 8:26 2.75 17850 United Kingdom
## 4 12/1/2010 8:26 3.39 17850 United Kingdom
## 5 12/1/2010 8:26 3.39 17850 United Kingdom
# Supprimer les lignes où Quantity ou UnitPrice est négatif
Retail_DF <- Retail_DF[Retail_DF$Quantity >= 0 & Retail_DF$UnitPrice >= 0, ]
# Vérifier le DataFrame après suppression
head(Retail_DF, 5)
## InvoiceNo StockCode Description Quantity
## 1 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6
## 2 536365 71053 WHITE METAL LANTERN 6
## 3 536365 84406B CREAM CUPID HEARTS COAT HANGER 8
## 4 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6
## 5 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6
## InvoiceDate UnitPrice CustomerID Country
## 1 12/1/2010 8:26 2.55 17850 United Kingdom
## 2 12/1/2010 8:26 3.39 17850 United Kingdom
## 3 12/1/2010 8:26 2.75 17850 United Kingdom
## 4 12/1/2010 8:26 3.39 17850 United Kingdom
## 5 12/1/2010 8:26 3.39 17850 United Kingdom
# Filtrer les anomalies où Quantity ou UnitPrice est négatif
anomalies <-Retail_DF[Retail_DF$Quantity < 0 | Retail_DF$UnitPrice < 0, ]
# Afficher les anomalies
print(anomalies)
## [1] InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice
## [7] CustomerID Country
## <0 lignes> (ou 'row.names' de longueur nulle)
summary(anomalies)
## InvoiceNo StockCode Description Quantity
## Length:0 Length:0 Length:0 Min. : NA
## Class :character Class :character Class :character 1st Qu.: NA
## Mode :character Mode :character Mode :character Median : NA
## Mean :NaN
## 3rd Qu.: NA
## Max. : NA
## InvoiceDate UnitPrice CustomerID Country
## Length:0 Min. : NA Min. : NA Length:0
## Class :character 1st Qu.: NA 1st Qu.: NA Class :character
## Mode :character Median : NA Median : NA Mode :character
## Mean :NaN Mean :NaN
## 3rd Qu.: NA 3rd Qu.: NA
## Max. : NA Max. : NA
Retail_DF <- na.omit(Retail_DF)
# Afficher les données nettoyées
head(Retail_DF, 5)
## InvoiceNo StockCode Description Quantity
## 1 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6
## 2 536365 71053 WHITE METAL LANTERN 6
## 3 536365 84406B CREAM CUPID HEARTS COAT HANGER 8
## 4 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6
## 5 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6
## InvoiceDate UnitPrice CustomerID Country
## 1 12/1/2010 8:26 2.55 17850 United Kingdom
## 2 12/1/2010 8:26 3.39 17850 United Kingdom
## 3 12/1/2010 8:26 2.75 17850 United Kingdom
## 4 12/1/2010 8:26 3.39 17850 United Kingdom
## 5 12/1/2010 8:26 3.39 17850 United Kingdom
summary(Retail_DF)
## InvoiceNo StockCode Description Quantity
## Length:397924 Length:397924 Length:397924 Min. : 1.00
## Class :character Class :character Class :character 1st Qu.: 2.00
## Mode :character Mode :character Mode :character Median : 6.00
## Mean : 13.02
## 3rd Qu.: 12.00
## Max. :80995.00
## InvoiceDate UnitPrice CustomerID Country
## Length:397924 Min. : 0.000 Min. :12346 Length:397924
## Class :character 1st Qu.: 1.250 1st Qu.:13969 Class :character
## Mode :character Median : 1.950 Median :15159 Mode :character
## Mean : 3.116 Mean :15294
## 3rd Qu.: 3.750 3rd Qu.:16795
## Max. :8142.750 Max. :18287
Anomalie est une dataframe vide ce qui montre que notre table à été correctement filtrer et netoyer des données éronnées et manquantes.
Dans ce jeu de données, le prix total du produit n’est pas mentionné, il est séparé en quantité et prix unitaire. J’ai ajouté une nouvelle colonne nommée “Total_price” qui contient le résultat de la multiplication de la quantité par le prix unitaire, ce qui donne le prix total.
Ici, je voulais voir comment les montants sont répartis par clients en regroupant les montants par customerID. La variation est très élevée, c’est pourquoi j’ai décidé de normaliser les données pour obtenir des résultats plus précis et une meilleure visualisation des résultats dans un graphique.
Retail_DF$Total_price <- Retail_DF$Quantity * Retail_DF$UnitPrice
head(Retail_DF, 5)
## InvoiceNo StockCode Description Quantity
## 1 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6
## 2 536365 71053 WHITE METAL LANTERN 6
## 3 536365 84406B CREAM CUPID HEARTS COAT HANGER 8
## 4 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6
## 5 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6
## InvoiceDate UnitPrice CustomerID Country Total_price
## 1 12/1/2010 8:26 2.55 17850 United Kingdom 15.30
## 2 12/1/2010 8:26 3.39 17850 United Kingdom 20.34
## 3 12/1/2010 8:26 2.75 17850 United Kingdom 22.00
## 4 12/1/2010 8:26 3.39 17850 United Kingdom 20.34
## 5 12/1/2010 8:26 3.39 17850 United Kingdom 20.34
# Agréger les données en sommant les 'Total_price' par 'CustomerID'
monetary <- Retail_DF %>%
group_by(CustomerID) %>%
summarise(Total_price = sum(Total_price, na.rm = TRUE)) %>%
arrange(Total_price) # Trier en ordre croissant
# Afficher les premières lignes
head(monetary)
## # A tibble: 6 × 2
## CustomerID Total_price
## <int> <dbl>
## 1 13256 0
## 2 16738 3.75
## 3 14792 6.2
## 4 16454 6.9
## 5 17956 12.8
## 6 16878 13.3
tail(monetary) # Affiche les dernières lignes de la table des monetry
## # A tibble: 6 × 2
## CustomerID Total_price
## <int> <dbl>
## 1 12415 124915.
## 2 14911 143825.
## 3 16446 168472.
## 4 17450 194551.
## 5 18102 259657.
## 6 14646 280206.
summary(monetary)
## CustomerID Total_price
## Min. :12346 Min. : 0.0
## 1st Qu.:13812 1st Qu.: 307.2
## Median :15299 Median : 674.5
## Mean :15300 Mean : 2053.8
## 3rd Qu.:16778 3rd Qu.: 1661.6
## Max. :18287 Max. :280206.0
Nous pouvons remarquer le présence d’articles avec un prix à 0, ce qui pourrait correspondre à des promotions ou des retours d’articles. Malheureusement, nous avons pas plus de détail mais cela peut àvoir des éffets sur nos graphiques vu le nombres d’articles concerné. Nous allons donc les supprimer.
Retail_DF <- Retail_DF[Retail_DF$UnitPrice > 0, ]
monetary <- monetary[monetary$Total_price > 0, ]
summary(Retail_DF)
## InvoiceNo StockCode Description Quantity
## Length:397884 Length:397884 Length:397884 Min. : 1.00
## Class :character Class :character Class :character 1st Qu.: 2.00
## Mode :character Mode :character Mode :character Median : 6.00
## Mean : 12.99
## 3rd Qu.: 12.00
## Max. :80995.00
## InvoiceDate UnitPrice CustomerID Country
## Length:397884 Min. : 0.001 Min. :12346 Length:397884
## Class :character 1st Qu.: 1.250 1st Qu.:13969 Class :character
## Mode :character Median : 1.950 Median :15159 Mode :character
## Mean : 3.116 Mean :15294
## 3rd Qu.: 3.750 3rd Qu.:16795
## Max. :8142.750 Max. :18287
## Total_price
## Min. : 0.00
## 1st Qu.: 4.68
## Median : 11.80
## Mean : 22.40
## 3rd Qu.: 19.80
## Max. :168469.60
summary(monetary)
## CustomerID Total_price
## Min. :12346 Min. : 3.75
## 1st Qu.:13813 1st Qu.: 307.42
## Median :15300 Median : 674.48
## Mean :15300 Mean : 2054.27
## 3rd Qu.:16779 3rd Qu.: 1661.74
## Max. :18287 Max. :280206.02
Pour comprendre le comportement des clients, les métriques RFM jouent un rôle essentiel, car la fréquence et la valeur monétaire influencent la valeur à vie d’un client, tandis que la récence a un impact sur la rétention, une mesure de l’engagement. Ici, je réalise une analyse approfondie de la méthode RFM combinée avec le regroupement K-Means, car elle permet de répondre à des questions cruciales telles que : “Qui sont les meilleurs clients ?” ou “Quels clients contribuent au taux d’attrition ?”, etc.
Dans cette analyse, je calcule la fréquence des clients en comptant le nombre de factures (Invoice numbers) pour chaque client. Plus ce nombre est élevé, plus le client achète souvent dans le magasin.
# Calculer la fréquence des achats (nombre de factures) par client
frequency <- aggregate(InvoiceNo ~ CustomerID, data = Retail_DF, length)
# Afficher les premières lignes du résultat
frequency <- frequency[order(desc(frequency$InvoiceNo)),]
head(frequency)
## CustomerID InvoiceNo
## 4011 17841 7847
## 1880 14911 5675
## 1290 14096 5111
## 327 12748 4595
## 1662 14606 2700
## 2177 15311 2379
tail(frequency)
## CustomerID InvoiceNo
## 4210 18113 1
## 4225 18133 1
## 4255 18174 1
## 4263 18184 1
## 4302 18233 1
## 4325 18268 1
Retail_DF$InvoiceDate <- lubridate::mdy_hm(Retail_DF$InvoiceDate)
head(Retail_DF$InvoiceDate)
## [1] "2010-12-01 08:26:00 UTC" "2010-12-01 08:26:00 UTC"
## [3] "2010-12-01 08:26:00 UTC" "2010-12-01 08:26:00 UTC"
## [5] "2010-12-01 08:26:00 UTC" "2010-12-01 08:26:00 UTC"
# Calcul de la différence entre la date maximale et chaque date
max_date <- max(Retail_DF$InvoiceDate, na.rm = TRUE)
Retail_DF$Diff <- as.numeric(difftime(max_date, Retail_DF$InvoiceDate, units = "days"))
# Calcul de la récence minimale (Diff minimale) pour chaque client
recency <- Retail_DF %>%
group_by(CustomerID) %>%
summarise(Diff = min(Diff, na.rm = TRUE)) %>%
ungroup()
# Afficher les premières lignes de la table des récences
head(recency)
## # A tibble: 6 × 2
## CustomerID Diff
## <int> <dbl>
## 1 12346 325.
## 2 12347 1.87
## 3 12348 75.0
## 4 12349 18.1
## 5 12350 310.
## 6 12352 35.9
summary(recency)
## CustomerID Diff
## Min. :12346 Min. : 0.00
## 1st Qu.:13813 1st Qu.: 17.07
## Median :15300 Median : 50.09
## Mean :15300 Mean : 92.05
## 3rd Qu.:16779 3rd Qu.:141.73
## Max. :18287 Max. :373.12
Ici, nous calculons la récence en soustrayant la date la plus récente de la dernière date de transaction des clients. Nous calculons la valeur monétaire en additionnant tous les montants associés à chaque client. Enfin, nous fusionnons toutes les colonnes dans un seul DataFrame.
RFM <- merge(frequency,recency, by = "CustomerID")
RFM <- merge(RFM, monetary, by = "CustomerID")
# Renommer les colonnes du DataFrame RFM
colnames(RFM) <- c("CustomerID", "frequency", "recency", "monetary")
head(RFM)
## CustomerID frequency recency monetary
## 1 12346 1 325.117361 77183.60
## 2 12347 182 1.873611 4310.00
## 3 12348 31 74.984028 1797.24
## 4 12349 73 18.124306 1757.55
## 5 12350 17 309.867361 334.40
## 6 12352 85 35.925694 2506.04
summary(frequency) # Voir la distribution complète
## CustomerID InvoiceNo
## Min. :12346 Min. : 1.00
## 1st Qu.:13813 1st Qu.: 17.00
## Median :15300 Median : 41.00
## Mean :15300 Mean : 91.72
## 3rd Qu.:16779 3rd Qu.: 100.00
## Max. :18287 Max. :7847.00
summary(recency)
## CustomerID Diff
## Min. :12346 Min. : 0.00
## 1st Qu.:13813 1st Qu.: 17.07
## Median :15300 Median : 50.09
## Mean :15300 Mean : 92.05
## 3rd Qu.:16779 3rd Qu.:141.73
## Max. :18287 Max. :373.12
summary(monetary)
## CustomerID Total_price
## Min. :12346 Min. : 3.75
## 1st Qu.:13813 1st Qu.: 307.42
## Median :15300 Median : 674.48
## Mean :15300 Mean : 2054.27
## 3rd Qu.:16779 3rd Qu.: 1661.74
## Max. :18287 Max. :280206.02
recency (Récence des achats) : Min = 0 : Certains clients ont
effectué un achat récemment (différence de 0 jours). 1er Quartile (Q1) =
17.07 : 25% des clients ont effectué leur dernier achat il y a moins de
17 jours. Médiane = 50.09 : La moitié des clients ont effectué leur
dernier achat dans les 50 derniers jours. Moyenne = 92.05 : En moyenne,
les clients ont effectué leur dernier achat il y a environ 92 jours, ce
qui suggère que certains clients n’ont pas acheté récemment. Max =
373.12 : Le client le plus ancien a effectué son dernier achat il y a
373 jours (environ 12 mois). Ce client pourrait être considéré comme
inactif.
monetary (Montant total dépensé) : Min = 3.75 : Le client ayant
le montant minimal a dépensé 3.75.1er Quartile (Q1) = 307.42 : 25% des
clients ont dépensé 307.42 ou moins. Médiane = 674.48 : La moitié des
clients ont dépensé moins de 674.48. Moyenne = 2054.27 : En moyenne, les
clients ont dépensé 2054.27, ce qui suggère que quelques clients ont des
dépenses très élevées, ce qui influence la moyenne. 3ème Quartile (Q3) =
1661.74 : 75% des clients ont dépensé moins de 1661.74. Max = 280206.02
: Il y a des clients ayant dépensé une somme extrêmement élevée,
280206.02. Cela pourrait aussi être une valeur extrême qu’il
conviendrait d’examiner.
J’ai calculé les valeurs monétaires (Monetary), de fréquence (Frequency) et de récence (Recency) regroupées par identifiant client. J’ai essayé de représenter ces valeurs sous forme de boîte à moustaches (box-plot). Ces données présentent des valeurs aberrantes qui pourraient affecter la précision des prédictions. Par conséquent, j’ai utilisé une mise à l’échelle standard (standard scaler) pour normaliser les données.
# Réaliser un box-plot pour plusieurs variables en même temps
RFM_long <- reshape2::melt(RFM, id.vars = "CustomerID", measure.vars = c("frequency", "recency", "monetary"))
ggplot(RFM_long, aes(x = variable, y = value, fill = variable)) +
geom_boxplot() +
labs(title = "Box-Plot des Variables RFM", x = "Variables", y = "Range") +
theme_minimal() +
theme(legend.position = "none")
La standardisation met chaque variable d’entrée sur une échelle séparée en soustrayant la moyenne (appelée centrage) et en divisant par l’écart-type, afin de décaler la distribution pour qu’elle ait une moyenne de zéro et un écart-type de un.
# Sélectionner les colonnes à normaliser
rfm_normalized <- RFM[, c('frequency', 'recency', 'monetary')]
# Appliquer la normalisation (centrage et réduction)
rfm_normalized <- scale(rfm_normalized)
# Afficher les données normalisées
head(rfm_normalized)
## frequency recency monetary
## [1,] -0.39653199 2.3303706 8.35770470
## [2,] 0.39460347 -0.9016372 0.25093734
## [3,] -0.26540457 -0.1706302 -0.02859271
## [4,] -0.08182617 -0.7391518 -0.03300799
## [5,] -0.32659736 2.1778908 -0.19132522
## [6,] -0.02937520 -0.5611616 0.05025720
rfm_normalized_df <- as.data.frame(rfm_normalized)
summary(rfm_normalized_df$frequency)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.39653 -0.32660 -0.22170 0.00000 0.03619 33.89766
summary(rfm_normalized_df$monetary)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.22811 -0.19433 -0.15349 0.00000 -0.04367 30.94278
summary(rfm_normalized_df$recency)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -0.9204 -0.7497 -0.4195 0.0000 0.4967 2.8104
Statistiques descriptives des variables normalisées : frequency :
Min : -0.3965 1st Qu. : -0.3266 Median : -0.2217 Mean : 0.0000 (c’est un bon signe que la normalisation a été correctement appliquée) Max : 33.8977 (ce nombre semble très élevé, il pourrait être utile de vérifier si ces valeurs extrêmes sont des cas rares ou des erreurs dans les données) recency :
Min : -0.2281 1st Qu. : -0.1943 Median : -0.1535 Mean : 0.0000 (c’est également une bonne indication de normalisation réussie) Max : 30.9428 (comme pour frequency, le max élevé pourrait signaler des valeurs extrêmes à vérifier) monetary :
Min : -0.9204 1st Qu. : -0.7497 Median : -0.4195 Mean : 0.0000 Max : 2.8104 (les valeurs sont désormais plus raisonnables ici) Interprétation générale : Moyenne proche de 0 et écart-type proche de 1 pour toutes les variables, ce qui confirme que la normalisation a bien été effectuée.
# Charger les bibliothèques nécessaires
library(ggplot2)
library(reshape2)
##
## Attachement du package : 'reshape2'
## Les objets suivants sont masqués depuis 'package:data.table':
##
## dcast, melt
# Conversion du DataFrame en format long pour le box-plot
rfm_normalized_plot <- melt(rfm_normalized, measure.vars = c("frequency", "recency", "monetary"))
# Renommer la colonne 'Var2' en 'variable'
colnames(rfm_normalized_plot) <- c("Var1", "variable", "value")
head(rfm_normalized_plot)
## Var1 variable value
## 1 1 frequency -0.39653199
## 2 2 frequency 0.39460347
## 3 3 frequency -0.26540457
## 4 4 frequency -0.08182617
## 5 5 frequency -0.32659736
## 6 6 frequency -0.02937520
# Tracer le box-plot
ggplot(rfm_normalized_plot, aes(x = variable, y = value, fill = variable)) +
geom_boxplot() +
labs(title = "Box-Plot des Variables RFM", x = "Variables", y = "Plage des valeurs normalisées") +
theme_minimal() +
theme(legend.position = "none")
head(rfm_normalized_plot)
## Var1 variable value
## 1 1 frequency -0.39653199
## 2 2 frequency 0.39460347
## 3 3 frequency -0.26540457
## 4 4 frequency -0.08182617
## 5 5 frequency -0.32659736
## 6 6 frequency -0.02937520
summary(rfm_normalized_plot)
## Var1 variable value
## Min. : 1 frequency:4338 Min. :-0.92037
## 1st Qu.:1085 recency :4338 1st Qu.:-0.33971
## Median :2170 monetary :4338 Median :-0.18534
## Mean :2170 Mean : 0.00000
## 3rd Qu.:3254 3rd Qu.: 0.03089
## Max. :4338 Max. :33.89766
# Fonction pour traiter les outliers
remove_outliers <- function(data, column_name) {
Q1 <- quantile(data[[column_name]], 0.25)
Q3 <- quantile(data[[column_name]], 0.75)
IQR <- Q3 - Q1
# Définir les limites
lower_limit <- Q1 - 1.5 * IQR
upper_limit <- Q3 + 1.5 * IQR
# Filtrer les données pour conserver uniquement les valeurs dans les limites
data <- data[data[[column_name]] >= lower_limit & data[[column_name]] <= upper_limit, ]
return(data)
}
# Appliquer la fonction sur chaque variable de rfm_normalized_plot
rfm_cleaned <- rfm_normalized_plot
rfm_cleaned <- remove_outliers(rfm_cleaned, "value")
# Vérifier le résultat
summary(rfm_cleaned)
## Var1 variable value
## Min. : 1 frequency:3961 Min. :-0.8925
## 1st Qu.:1085 recency :3071 1st Qu.:-0.3519
## Median :2172 monetary :4185 Median :-0.1998
## Mean :2169 Mean :-0.2237
## 3rd Qu.:3250 3rd Qu.:-0.0862
## Max. :4338 Max. : 0.5826
# Refaire le box-plot
ggplot(rfm_cleaned, aes(x = variable, y = value, fill = variable)) +
geom_boxplot() +
labs(title = "Box-Plot des Variables RFM (Après traitement des outliers)",
x = "Variables",
y = "Plage des valeurs normalisées") +
theme_minimal() +
theme(legend.position = "none")
#summary(rfm_normalized_plot)
# Calcul des seuils pour chaque variable
thresholds <- data.frame(
variable = c("frequency", "monetary", "recency"),
lower = sapply(rfm_normalized, function(x) quantile(x, 0.25) - 1.5 * IQR(x)),
upper = sapply(rfm_normalized, function(x) quantile(x, 0.75) + 1.5 * IQR(x))
)
head(thresholds)
## variable lower upper
## 1 frequency -0.39653199 -0.39653199
## 2 monetary 0.39460347 0.39460347
## 3 recency -0.26540457 -0.26540457
## 4 frequency -0.08182617 -0.08182617
## 5 monetary -0.32659736 -0.32659736
## 6 recency -0.02937520 -0.02937520
# 3. Exploration de la segmentation client avec l’analyse RFM
# Création des segments
rfm_normalized <- as.data.frame(rfm_normalized)
rfm_segmented <- rfm_normalized %>%
mutate(
segment_frequency = case_when(
frequency < thresholds$lower[1] ~ "Outlier négatif",
frequency > thresholds$upper[1] ~ "Outlier positif",
TRUE ~ "Normal"
),
segment_monetary = case_when(
monetary < thresholds$lower[2] ~ "Outlier négatif",
monetary > thresholds$upper[2] ~ "Outlier positif",
TRUE ~ "Normal"
),
segment_recency = case_when(
recency < thresholds$lower[3] ~ "Outlier négatif",
recency > thresholds$upper[3] ~ "Outlier positif",
TRUE ~ "Normal"
)
)
Interprétation des segments :
segment_frequency :
“Normal” : Valeur de fréquence dans la moyenne. “Outlier positif” : Fréquence très élevée (client très actif).
segment_monetary :
“Outlier positif” : Client avec une valeur monétaire élevée (dépense beaucoup). “Outlier négatif” : Client avec une valeur monétaire très faible.
segment_recency :
“Outlier positif” : Client récemment actif (achat récent). “Outlier négatif” : Client qui n’a pas acheté depuis longtemps.
head(rfm_segmented)
## frequency recency monetary segment_frequency segment_monetary
## 1 -0.39653199 2.3303706 8.35770470 Normal Outlier positif
## 2 0.39460347 -0.9016372 0.25093734 Outlier positif Outlier négatif
## 3 -0.26540457 -0.1706302 -0.02859271 Outlier positif Outlier négatif
## 4 -0.08182617 -0.7391518 -0.03300799 Outlier positif Outlier négatif
## 5 -0.32659736 2.1778908 -0.19132522 Outlier positif Outlier négatif
## 6 -0.02937520 -0.5611616 0.05025720 Outlier positif Outlier négatif
## segment_recency
## 1 Outlier positif
## 2 Outlier négatif
## 3 Outlier positif
## 4 Outlier négatif
## 5 Outlier positif
## 6 Outlier négatif
table(rfm_segmented$segment_frequency)
##
## Normal Outlier positif
## 71 4267
table(rfm_segmented$segment_monetary)
##
## Outlier négatif Outlier positif
## 4106 232
table(rfm_segmented$segment_recency)
##
## Outlier négatif Outlier positif
## 2511 1827
ggplot(rfm_segmented, aes(x = segment_frequency, fill = segment_frequency)) +
geom_bar() +
labs(title = "Distribution des Segments - Fréquence", x = "Segment", y = "Nombre de clients") +
theme_minimal()
best_clients <- rfm_segmented %>% filter(segment_frequency == "Outlier positif")
summary(best_clients)
## frequency recency monetary segment_frequency
## Min. :-0.39216 Min. :-0.92037 Min. :-0.227836 Length:4267
## 1st Qu.:-0.32223 1st Qu.:-0.75022 1st Qu.:-0.193844 Class :character
## Median :-0.21732 Median :-0.42115 Median :-0.151953 Mode :character
## Mean : 0.00660 Mean :-0.01815 Mean : 0.001249
## 3rd Qu.: 0.04056 3rd Qu.: 0.44803 3rd Qu.:-0.040231
## Max. :33.89766 Max. : 2.81036 Max. :30.942777
## segment_monetary segment_recency
## Length:4267 Length:4267
## Class :character Class :character
## Mode :character Mode :character
##
##
##
at_risk_clients <- rfm_segmented %>% filter(segment_monetary == "Outlier négatif")
summary(at_risk_clients)
## frequency recency monetary segment_frequency
## Min. :-0.39653 Min. :-0.92037 Min. :-0.22811 Length:4106
## 1st Qu.:-0.33097 1st Qu.:-0.73025 1st Qu.:-0.19546 Class :character
## Median :-0.23481 Median :-0.38921 Median :-0.15919 Mode :character
## Mean :-0.09475 Mean : 0.03994 Mean :-0.11104
## 3rd Qu.:-0.01626 3rd Qu.: 0.58942 3rd Qu.:-0.07113
## Max. : 4.86168 Max. : 2.81036 Max. : 0.39403
## segment_monetary segment_recency
## Length:4106 Length:4106
## Class :character Class :character
## Mode :character Mode :character
##
##
##
ggplot(rfm_segmented, aes(x = segment_frequency, y = frequency, fill = segment_frequency)) +
geom_boxplot() +
labs(title = "Comparaison des Fréquences par Segment", x = "Segment", y = "Fréquence") +
theme_minimal()
# 1. Préparation des données
rfm_normalized <- rfm_normalized[, c("frequency", "recency", "monetary")]
# 2. Déterminer le nombre optimal de clusters (méthode de l'inertie - Elbow method)
set.seed(123)
wss <- sapply(1:10, function(k) {
kmeans(rfm_normalized, centers = k, nstart = 50)$tot.withinss
})
## Warning: Les étapes de transfer (quick-TRANSfer stage) ont dépassé le maximum
## (= 216900)
# Visualisation de l'inertie
plot(1:10, wss, type = "b", pch = 19, frame = FALSE,
xlab = "Nombre de clusters K",
ylab = "Inertie totale (Within Sum of Squares)",
main = "Méthode du coude pour choisir K")
# 3. Appliquer K-Means avec le nombre optimal de clusters (par exemple, K = 4)
set.seed(123)
kmeans_model <- kmeans(rfm_normalized, centers = 6, nstart = 50)
# 4. Ajouter les labels des clusters à vos données
rfm_segmented$cluster <- as.factor(kmeans_model$cluster)
# 5. Résumé des clusters
summary_clusters <- aggregate(rfm_normalized, by = list(Cluster = rfm_segmented$cluster), mean)
print(summary_clusters)
## Cluster frequency recency monetary
## 1 1 -0.29710770 2.0190546 -0.18246118
## 2 2 -0.05717003 -0.5849968 -0.07017814
## 3 3 24.98099546 -0.9060289 7.66150356
## 4 4 3.21311460 -0.8500352 21.00393314
## 5 5 -0.23413698 0.6097588 -0.14924641
## 6 6 1.75188535 -0.7631732 1.13869977
# 6. Visualisation des clusters (exemple : graphique en 2D avec PCA)
pca_res <- prcomp(rfm_normalized, scale = TRUE)
pca_data <- data.frame(pca_res$x[, 1:2], Cluster = rfm_segmented$cluster)
ggplot(pca_data, aes(x = PC1, y = PC2, color = Cluster)) +
geom_point(alpha = 0.6, size = 3) +
labs(title = "Visualisation des Clusters K-Means",
x = "Composante Principale 1",
y = "Composante Principale 2") +
theme_minimal()
Cluster Frequency (Fréquence) Recency (Récence) Monetary (Valeur
Monétaire) Profil probable
1 Faible Très récent Faible Nouveaux
clients ou clients irréguliers
2 Faible Moyennement récent Faible
Clients dormants (achats peu fréquents et de faible valeur)
3 Très
élevé (24.98) Ancien Très élevé (7.66) Clients fidèles avec des dépenses
élevées
4 Élevé (3.21) Ancien Très élevé (21.00) Clients VIP avec
une forte valeur mais inactifs récemment
5 Faible Moyennement actif
Faible Clients standards (ni trop actifs, ni trop dépensiers)
6
Moyen à élevé Récemment actif Moyen Clients engagés, mais avec une
valeur monétaire modérée
Voici quelques applications métiers basées sur l’analyse RFM et le clustering des clients :